﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text.RegularExpressions;
using CommonUtils.Logging;

namespace CommonUtils.CommandLine
{
    /// <summary>
    /// Generic command line analyzer
    /// </summary>
    public class CommandLineHandler
    {
        #region Delegates

        /// <summary>
        /// Command delegate
        /// </summary>
        protected delegate void CommandHandler();

        /// <summary>
        /// Argument delegate
        /// </summary>
        /// <param name="value">Argument value</param>
        protected delegate void ArgumentHandler(string value);

        #endregion

        #region Constants

        /// <summary>
        /// Command line item format
        /// </summary>
        private const string ItemNameFormat = @"[\w_-]*";

        /// <summary>
        /// Command line command format
        /// </summary>
        private const string CommandFormat = ItemNameFormat;

        /// <summary>
        /// Command line argument format
        /// </summary>
        private const string ArgumentFormat = CommandFormat + ArgumentValueFormat;

        /// <summary>
        /// Command line item start format
        /// </summary>
        private const string ItemStartFormat = "^[/-]{0,1}";

        /// <summary>
        /// Format string for regex for matching the whole line
        /// </summary>
        private const string RegexWrapFormat = "^{0}$";

        /// <summary>
        /// Command line argument value format
        /// </summary>
        private const string ArgumentValueFormat = @":[\w\s\.\\:]*";

        #endregion

        #region Properties

        /// <summary>
        /// Default command handler (if not specified)
        /// </summary>
        protected CommandHandler DefaultCommand
        {
            get;
            set;
        }

        /// <summary>
        /// Command line item name regular expression
        /// </summary>
        private readonly Regex itemNameRegex = new Regex(ItemNameFormat);

        /// <summary>
        /// Command line item name regular expression
        /// </summary>
        private Regex ItemNameRegex
        {
            [DebuggerStepThrough]
            get
            {
                return itemNameRegex;
            }
        }

        /// <summary>
        /// Command line command regular expression
        /// </summary>
        private readonly Regex commandRegex = new Regex(string.Format(RegexWrapFormat, CommandFormat));

        /// <summary>
        /// Command line command regular expression
        /// </summary>
        private Regex CommandRegex
        {
            [DebuggerStepThrough]
            get
            {
                return commandRegex;
            }
        }

        /// <summary>
        /// Command line argument regular expression
        /// </summary>
        private readonly Regex argumentRegex = new Regex(string.Format(RegexWrapFormat, ArgumentFormat));

        /// <summary>
        /// Command line argument regular expression
        /// </summary>
        private Regex ArgumentRegex
        {
            [DebuggerStepThrough]
            get
            {
                return argumentRegex;
            }
        }

        /// <summary>
        /// Command line item start regex
        /// </summary>
        private readonly Regex itemStartRegex = new Regex(ItemStartFormat);

        /// <summary>
        /// Command line item start regex
        /// </summary>
        public Regex ItemStartRegex
        {
            [DebuggerStepThrough]
            get
            {
                return itemStartRegex;
            }
        }

        /// <summary>
        /// Command handlers map
        /// </summary>
        public Dictionary<string, Delegate> CommandMap
        {
            get;
            private set;
        }

        /// <summary>
        /// Argument handlers map
        /// </summary>
        protected Dictionary<string, Delegate> ArgumentMap
        {
            get;
            private set;
        }

        /// <summary>
        /// Command to execute
        /// </summary>
        protected MethodInfo Command
        {
            [DebuggerStepThrough]
            get;
            [DebuggerStepThrough]
            set;
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="arguments">Command line parameters</param>
        public CommandLineHandler(IEnumerable<string> arguments)
        {
            DefaultCommand = HelpHandler;

            CommandMap = CreateDelegateMap<CommandLineCommandHandlerAttribute>(typeof (CommandHandler));
            ArgumentMap = CreateDelegateMap<CommandLineArgumentHandlerAttribute>(typeof (ArgumentHandler));

            foreach (string argument in arguments)
            {
                string truncArgument = ItemStartRegex.Replace(argument, "");
                if (ArgumentRegex.IsMatch(truncArgument))
                {
                    string argumentName = ItemNameRegex.Match(truncArgument).Value;
                    string argumentValue = truncArgument.Substring(argumentName.Length + 1);
                    Logger.Instance.LogMessage(string.Format("'{0}' argument with '{1}' value found",
                                                             argumentName,
                                                             argumentValue));
                    if (ArgumentMap.ContainsKey(argumentName))
                    {
                        ArgumentMap[argumentName].Method.Invoke(this, new object[] {argumentValue});
                    }
                    else
                    {
                        Logger.Instance.LogMessage(MessageType.Error,
                                                   string.Format("'{0}' is not valid argument name", argumentName));
                    }
                }
                else if (CommandRegex.IsMatch(truncArgument))
                {
                    Logger.Instance.LogMessage(string.Format("'{0}' command found", truncArgument));
                    if (CommandMap.ContainsKey(truncArgument))
                    {
                        if (Command == null)
                        {
                            Command = CommandMap[truncArgument].Method;
                        }
                        else
                        {
                            Logger.Instance.LogMessage(MessageType.Error, "Only one command allowed. Ignoring");
                        }
                    }
                    else
                    {
                        Logger.Instance.LogMessage(MessageType.Error,
                                                   string.Format("'{0}' is not valid argument name", truncArgument));
                    }
                }
                else
                {
                    Logger.Instance.LogMessage(MessageType.Error,
                                               string.Format("Error: '{0}' - unknown argument or command", truncArgument));
                }
            }
        }

        #endregion

        #region Implementation

        /// <summary>
        /// Execute
        /// </summary>
        public void Execute()
        {
            if (Command != null)
            {
                Command.Invoke(this, new object[] {});
            }
            else
            {
                DefaultCommand.Invoke();
            }
        }

        /// <summary>
        /// Create dictionary of command handlers
        /// </summary>
        /// <returns></returns>
        private Dictionary<string, Delegate> CreateDelegateMap<AttributeType>(Type delegateType)
            where AttributeType : CommandLineItemHandlerAttribute
        {
            Dictionary<string, Delegate> retValue = new Dictionary<string, Delegate>();

            MethodInfo[] methods =
                GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

            foreach (MethodInfo method in methods)
            {
                object[] attributes = method.GetCustomAttributes(typeof (AttributeType), true);
                foreach (object attribute in attributes)
                {
                    AttributeType commandHandlerAttribute = (AttributeType) (attribute);
                    retValue.Add(commandHandlerAttribute.ItemName, Delegate.CreateDelegate(delegateType, this, method));
                }
            }

            return retValue;
        }

        #endregion

        #region Command line commands

        /// <summary>
        /// Show help information
        /// </summary>
        [CommandLineItemDescription("Show program usage information")]
        [CommandLineCommandHandler("help")]
        protected void HelpHandler()
        {
            Logger.Instance.PrintMessage("Commands:");

            CommandLineUtils.Instance.WriteCommandLineItemDictionaryInfo(CommandMap);

            Logger.Instance.PrintMessage("Arguments:");

            CommandLineUtils.Instance.WriteCommandLineItemDictionaryInfo(ArgumentMap);
        }

        #endregion
    }
}